// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2016-2018, LomboTech Co.Ltd.
 * Authors:
 *	lomboswer <lomboswer@lombotech.com>
 *
 * This program is free software; you can redistribute  it and/or modify it
 * under  the terms of  the GNU General  Public License as published by the
 * Free Software Foundation;  either version 2 of the  License, or (at your
 * option) any later version.
 */
#include <linux/iio/iio.h>
#include <linux/backlight.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <linux/spi/spi.h>

#include <drm/drmP.h>
#include <drm/drm_panel.h>
#include <drm/lombo_drm.h>
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_gem_framebuffer_helper.h>

#include "../lombo/lombo_vo_manage.h"
#include "../lombo/lombo_drv.h"
#include "../lombo/lombo_wb.h"
#include "../lombo/lombo_crtc.h"

#define MAX_BUF_SIZE	0x10000

#define ACTIVE(flag)		(((flag) & OF_GPIO_ACTIVE_LOW) ? 1 : 0)
#define INACTIVE(flag)		(((flag) & OF_GPIO_ACTIVE_LOW) ? 0 : 1)

typedef enum spi_rs_flag {
	CMD_FLAG = 0,
	DATA_FLAG = 1,
} spi_rs_flag_t;

typedef enum spi_if_mode {
	SPI_3WIRE_MODE = 0,
	SPI_4WIRE_MODE = 1,
} spi_if_mode_t;

typedef enum init_panel_type {
	PANEL_SIMPLE_NONE = 0,
	PANEL_SIMPLE_DSI = 1,
	PANEL_SIMPLE_CPU = 2,
	PANEL_SIMPLE_I2C = 3,
	PANEL_SIMPLE_SPI = 4,
	PANEL_SIMPLE_SPI_GPIO = 5,
} init_panel_type_t;

struct panel_lombo_spi_desc {
	struct drm_display_mode *modes;
	unsigned int num_modes;

	unsigned int bpc;
	init_panel_type_t init_panel;

	struct {
		unsigned int width;
		unsigned int height;
	} size;

	struct panel_delay {
		unsigned int reset;
		unsigned int prepare;
		unsigned int enable;
		unsigned int disable;
		unsigned int unprepare;
	} delay;
};

struct panel_lombo_spi {
	struct drm_panel base;
	bool prepared;
	bool enabled;

	struct device *dev;
	struct panel_lombo_spi_desc *desc;
	struct backlight_device *backlight;
	struct regulator *supply;
	int reset_gpio;
	int power_gpio;
	int sel_gpio;
	enum of_gpio_flags reset_gpio_flag;
	enum of_gpio_flags power_gpio_flag;
	enum of_gpio_flags sel_gpio_flag;

	enum spi_if_mode spi_mode;
	int spi_data;
	enum of_gpio_flags spi_data_flag;
	int spi_clk;
	enum of_gpio_flags spi_clk_flag;
	int spi_cs;
	enum of_gpio_flags spi_cs_flag;
	int spi_rs;
	enum of_gpio_flags spi_rs_flag;

	int (*transfer)(unsigned int width, unsigned int height,
		unsigned char *inbuf, unsigned char *outbuf);
	uint32_t buf_size;

	u32 *init_code32;
	u8 *init_code8;
	u32 init_code_num;

	int orientation;

	struct workqueue_struct *spi_workq;
	struct work_struct spi_flush_work;
	struct work_struct spi_write_work;
};

struct lombo_spi_client {
	struct spi_device  *spi;
	struct mutex  lock;
	struct device  dev;

	const char  *name;

	u32  flags;
	u32  freq;
	u32  spi_mode;
	u32  page_size;
};

struct lombo_spi_client *spi_client;
struct panel_lombo_spi *g_panel;
struct drm_framebuffer *wb_fb;
struct drm_gem_cma_object *cma_obj;
u_char *g_buf;

static inline struct panel_lombo_spi *to_panel_lombo_spi(
	struct drm_panel *panel)
{
	return container_of(panel, struct panel_lombo_spi, base);
}

static inline void hw_init_spi(struct drm_panel *panel)
{
	struct panel_lombo_spi *p = to_panel_lombo_spi(panel);

	if (p->spi_cs && p->spi_clk && p->spi_data) {
		gpio_direction_output(p->spi_cs, 1);
		udelay(2);
		gpio_direction_output(p->spi_clk, 1);
		udelay(2);
		gpio_direction_output(p->spi_data, 1);
		udelay(2);
	}
}

static void spi_write_8bit_mode(struct drm_panel *panel,
	u16 value, spi_rs_flag_t rs, spi_if_mode_t if_mode)
{
	u32 i = 0, cnt = 0;
	struct panel_lombo_spi *p = to_panel_lombo_spi(panel);

	/* cs low */
	gpio_direction_output(p->spi_cs, 0);
	udelay(2);

	/* rs wire */
	if (if_mode == SPI_3WIRE_MODE) {
		cnt = 9;
		if (rs == DATA_FLAG)
			value |= 0x100;
	} else if (if_mode == SPI_4WIRE_MODE) {
		cnt = 8;
		gpio_direction_output(p->spi_rs, rs);
	}
	/* send value */
	for (i = 0; i < cnt; i++) {
		gpio_direction_output(p->spi_data,
			(value >> (cnt - 1 - i)) & 0x1);
		gpio_direction_output(p->spi_clk, 0);
		udelay(1);
		gpio_direction_output(p->spi_clk, 1);
		udelay(1);
	}

	/* cs high */
	gpio_direction_output(p->spi_cs, 1);
	udelay(2);
}

static int argb8888_to_rgb888(unsigned int w, unsigned int h,
	unsigned char *inbuf, unsigned char *outbuf)
{
	unsigned int len;
	int i;
	unsigned int *pin;
	uint16_t *dst = (uint16_t *)outbuf;

	if (inbuf == NULL || outbuf == NULL) {
		DRM_ERROR("err inbuf or outbuf pointer\n");
		return -1;
	}
	pin = (unsigned int *)inbuf;
	len = w * h;
	for (i = 0; i < len / 4; i++) {
		uint32_t src0 = *pin++;
		uint32_t src1 = *pin++;
		uint32_t src2 = *pin++;
		uint32_t src3 = *pin++;
		*dst++ = (uint16_t)(((src0 >> 16) & 0xFF) | (src0 & 0xFF00));
		*dst++ = (uint16_t)((src0 & 0xFF) | ((src1 >> 8) & 0xFF00));
		*dst++ = (uint16_t)(((src1 >> 8) & 0xFF) |
			 ((src1 << 8) & 0xFF00));
		*dst++ = (uint16_t)(((src2 >> 16) & 0xFF) | (src2 & 0xFF00));
		*dst++ = (uint16_t)((src2 & 0xFF) | ((src3 >> 8) & 0xFF00));
		*dst++ = (uint16_t)(((src3 >> 8) & 0xFF) |
			 ((src3 << 8) & 0xFF00));
	}

	return 0;
}

static int argb8888_to_2p3t(unsigned int w, unsigned int h,
	unsigned char *inbuf, unsigned char *outbuf)
{
	unsigned int pixlen = 0, pixcount = 0, outcount = 0;
	unsigned int pixin[2], *inp = NULL;
	unsigned int pixout[3], *outp = NULL;

	pixlen = w * h;
	if (inbuf == NULL || outbuf == NULL) {
		DRM_ERROR("err inbuf or outbuf pointer\n");
		return -1;
	}

	inp = (unsigned int *)inbuf;
	outp = (unsigned int *)outbuf;
	for (pixcount = 0; pixcount < pixlen; pixcount += 2) {
		pixin[0] = inp[pixcount + 0];
		pixin[1] = inp[pixcount + 1];

		pixout[0] = ((((pixin[0] >> 8) & 0xFF) | 0x100) << 16) |
			(((pixin[0] >> 16) & 0xFF) | 0x100);
		pixout[1] = ((((pixin[1] >> 16) & 0xFF) | 0x100) << 16) |
			(((pixin[0] >>  0) & 0xFF) | 0x100);
		pixout[2] = ((((pixin[1] >> 0) & 0xFF) | 0x100) << 16) |
			(((pixin[1] >> 8) & 0xFF) | 0x100);

		outp[outcount++] = pixout[0];
		outp[outcount++] = pixout[1];
		outp[outcount++] = pixout[2];
	}

	return 0;
}

static void spi_panel_flush(void)
{
	queue_work(g_panel->spi_workq, &g_panel->spi_flush_work);
}

static int spi_write_buf(struct spi_device *spi,
		size_t buf, u32 len, u32 bit, u32 reduce_bit)
{
	int err = 0;
	struct spi_transfer t;
	struct spi_message m;
	struct lombo_spi_operate_para esop;

	memset(&t, 0, sizeof(t));
	memset(&esop, 0, sizeof(esop));
	spi_message_init(&m);

	esop.inst_len_invalid_flag = 1;
	esop.data_reduce_bit = reduce_bit;
	esop.ff = FRAME_FORMAT_STANDARD_SPI;

	t.bits_per_word = bit;
	t.len = len;
	t.tx_buf = (size_t *)buf;
	t.esop = &esop;
	spi_message_add_tail(&t, &m);

	err = spi_sync(spi, &m);
	if (err < 0) {
		DRM_ERROR("write from:0x%p, len:%d failed\n",
				(size_t *)buf, len);
		return err;
	}

	return err;
}

static int panel_spi_write(const u_char *buf, size_t len,
		size_t bit, size_t reduce_bit)
{
	int err = 0;
	unsigned int i;
	unsigned int page_size;

	if (!spi_client)
		return -ENODEV;

	mutex_lock(&spi_client->lock);

	for (i = 0; i < len; i += page_size) {
		page_size = len - i;
		if (page_size > spi_client->page_size)
			page_size = spi_client->page_size;
		err = spi_write_buf(spi_client->spi,
			(size_t)(buf + i), page_size, bit, reduce_bit);
		if (err) {
			DRM_ERROR("%s err=%d\n", __func__, err);
			err = __LINE__;
			goto out;
		}
	}

out:
	mutex_unlock(&spi_client->lock);

	return err;
}

static int spi_write_8bits(const u_char *buf)
{
	int err;

	err = panel_spi_write(buf, 1, 8, 0);
	if (err) {
		DRM_ERROR("write 8bit err, %d\n", err);
		return -1;
	}
	return 0;
}

static int spi_write_9bits(const u_char *buf)
{
	int err;

	err = panel_spi_write(buf, 2, 16, 7);
	if (err) {
		DRM_ERROR("write 9bit err, %d\n", err);
		return -1;
	}
	return 0;
}

/* spi io_analog addr8_data8 + bit9-D/C, 0:command , 1:data */
static int spi_write_reg_array_gpio(struct drm_panel *panel,
	uint32_t *reg, uint32_t len, uint32_t if_mode)
{
	uint32_t i = 0, j = 0;

	while (i < len) {
		if (reg[i] == 0) {
			mdelay(reg[i + 1]);
			i += reg[i] + 2;
		} else if (reg[i] == 1) {  /* write one-commond */
			spi_write_8bit_mode(panel, reg[i + 1],
					    CMD_FLAG, if_mode);
			i += reg[i] + 1;
		} else if (reg[i] > 1) {  /* write one-commond + N-data */
			/* first:  write one-commond */
			spi_write_8bit_mode(panel, reg[i + 1],
					    CMD_FLAG, if_mode);
			for (j = 1; j <= (reg[i]-1); j++) {
				/* second: write N-data */
				spi_write_8bit_mode(panel, reg[i + 1 + j],
						    DATA_FLAG, if_mode);
			}
			i += reg[i] + 1;
		}
	}

	return 0;
}

static int spi_write_reg_array_api(struct drm_panel *panel,
	uint32_t *reg, uint32_t len, uint32_t if_mode)
{
	struct panel_lombo_spi *p = to_panel_lombo_spi(panel);
	uint32_t i = 0, j = 0;

	while (i < len) {
		if (reg[i] == 0) {
			mdelay(reg[i + 1]);
			i += reg[i] + 2;
		} else if (reg[i] == 1) {  /* write one-commond */
			if (if_mode == SPI_4WIRE_MODE) {
				gpio_direction_output(p->spi_rs, 0);
				spi_write_8bits((u_char *)&reg[i + 1]);
			} else
				spi_write_9bits((u_char *)&reg[i + 1]);
			i += reg[i] + 1;
		} else if (reg[i] > 1) {  /* write one-commond + N-data */
			if (if_mode == SPI_4WIRE_MODE) {
				gpio_direction_output(p->spi_rs, 0);
				spi_write_8bits((u_char *)&reg[i + 1]);
				gpio_direction_output(p->spi_rs, 1);
				for (j = 1; j <= (reg[i] - 1); j++)
					spi_write_8bits((u_char *)&reg[i +
							 1 + j]);
			} else {
				spi_write_9bits((u_char *)&reg[i + 1]);
				for (j = 1; j <= (reg[i]-1); j++) {
					/* second: write N-data */
					reg[i + 1 + j] |= 0x100;
					spi_write_9bits((u_char *)&reg[i +
							 1 + j]);
				}
			}
			i += reg[i] + 1;
		}
	}

	return 0;
}

static void panel_lombo_spi_flush_worker(struct work_struct *work)
{
	struct panel_lombo_spi *p = container_of(work,
		struct panel_lombo_spi, spi_flush_work);
	struct drm_device *drm = p->base.drm;
	struct lombo_drv *drv = drm->dev_private;
	struct lombo_wb *wb = drv->wb;
	struct lombo_wb_funcs *funcs = drv->wb_funcs;
	u_char *cma_map_buf;
	uint32_t map_offset;
	int ret = 0;
	static int is_set_para;
	struct drm_lombo_wb_set_para para;
	struct lombo_wb_que_buf que_buf = {
		.fb = wb_fb,
		.release_fence = -1,
	};
	struct drm_lombo_wb_start_arg start_arg = {
		.count = 1
	};
	struct drm_lombo_wb_dequeue_buf deque_buf = {
		.fb_state = LOMBO_WB_FB_STATE_WRITTEN,
		.acquire_fence = -1,
	};

	if (is_set_para == 0) {
		para.key = LOMBO_WB_PARA_KEY_RUN_MODE;
		para.value = 0;
		funcs->set_para(wb, &para);
		para.key = LOMBO_WB_PARA_KEY_CREATE_FB_QUEUE;
		para.value = 1;
		funcs->set_para(wb, &para);
		para.key = LOMBO_WB_PARA_KEY_DST_WIDTH;
		para.value = wb_fb->width;
		funcs->set_para(wb, &para);
		para.key = LOMBO_WB_PARA_KEY_DST_HEIGHT;
		para.value = wb_fb->height;
		funcs->set_para(wb, &para);
		is_set_para = 1;
	}
	funcs->queue_buf(wb, &que_buf);
	funcs->start(wb, &start_arg);
	ret = funcs->dequeue_buf(wb, &deque_buf);
	if (ret)
		DRM_ERROR("wb dequeue buf failed, ret = %d\n", ret);
	funcs->stop(wb);
	lombo_fb_kmap(cma_obj->paddr, wb_fb->pitches[0] * wb_fb->height,
		      1, (void **)&cma_map_buf, &map_offset);
	g_panel->transfer(wb_fb->width, wb_fb->height,
			  cma_map_buf, g_buf);
	lombo_fb_kunmap((void **)&cma_map_buf);
	queue_work(g_panel->spi_workq, &g_panel->spi_write_work);
}

static void panel_lombo_spi_write_worker(struct work_struct *work)
{
	if (g_panel->spi_mode == SPI_4WIRE_MODE)
		panel_spi_write(g_buf, g_panel->buf_size, 32, 0);
	else
		panel_spi_write(g_buf, g_panel->buf_size, 16, 7);
}

static int panel_lombo_spi_disable(struct drm_panel *panel)
{
	struct panel_lombo_spi *p = to_panel_lombo_spi(panel);

	DRM_DEBUG_KMS("[%d] p->enabled:%d\n", __LINE__, p->enabled);

	if (!p->enabled)
		return 0;

	if (p->backlight) {
		p->backlight->props.power = FB_BLANK_POWERDOWN;
		p->backlight->props.state |= BL_CORE_FBBLANK;
		backlight_update_status(p->backlight);
	}

	if (p->desc && p->desc->delay.disable)
		msleep(p->desc->delay.disable);

	p->enabled = false;

	return 0;
}

static int panel_lombo_spi_unprepare(struct drm_panel *panel)
{
	struct panel_lombo_spi *p = to_panel_lombo_spi(panel);

	DRM_DEBUG_KMS("[%d] p->prepared:%d\n", __LINE__, p->prepared);

	if (!p->prepared)
		return 0;

	if (p->reset_gpio)
		gpio_direction_output(p->reset_gpio,
			INACTIVE(p->reset_gpio_flag));

	if (p->supply)
		regulator_disable(p->supply);

	if (p->power_gpio)
		gpio_direction_output(p->power_gpio,
			INACTIVE(p->power_gpio_flag));

	if (p->desc && p->desc->delay.unprepare)
		msleep(p->desc->delay.unprepare);

	p->prepared = false;

	return 0;
}

static uint32_t boot_cnt;

static int panel_lombo_spi_prepare(struct drm_panel *panel)
{
	struct panel_lombo_spi *p = to_panel_lombo_spi(panel);
	int err;
	int width, height, pitch;
	struct drm_mode_fb_cmd2 cmd_mode = { 0 };
	struct drm_gem_object *objs;

	DRM_DEBUG_KMS("[%d] p->prepared:%d\n", __LINE__, p->prepared);

	if (p->prepared)
		return 0;
	p->prepared = true;

	if (p->supply) {
		err = regulator_enable(p->supply);
		if (err < 0) {
			DRM_ERROR("failed to enable supply\n");
			return err;
		}
	}

	if (p->power_gpio)
		gpio_direction_output(p->power_gpio,
			ACTIVE(p->power_gpio_flag));

	if (p->sel_gpio)
		gpio_direction_output(p->sel_gpio,
			ACTIVE(p->sel_gpio_flag));

	if (p->reset_gpio) {
		gpio_direction_output(p->reset_gpio,
			INACTIVE(p->reset_gpio_flag));
		msleep(p->desc->delay.reset);
		gpio_direction_output(p->reset_gpio,
			ACTIVE(p->reset_gpio_flag));
		msleep(p->desc->delay.reset);
		gpio_direction_output(p->reset_gpio,
			INACTIVE(p->reset_gpio_flag));
		msleep(p->desc->delay.reset);
	} else {
		DRM_DEBUG_KMS("use gpadc for panel reset temporarily\n");
		lombo_write_ainx_output(3, 1);
		mdelay(p->desc->delay.reset);
		lombo_write_ainx_output(3, 0);
		mdelay(p->desc->delay.reset);
		lombo_write_ainx_output(3, 1);
		mdelay(p->desc->delay.reset);
	}

	if (p->desc && p->desc->delay.prepare)
		msleep(p->desc->delay.prepare);

	if (p->desc->init_panel == PANEL_SIMPLE_SPI &&
		p->init_code32 && p->init_code_num) {
		spi_write_reg_array_api(&p->base, p->init_code32,
			p->init_code_num, p->spi_mode);
	} else if (p->desc->init_panel == PANEL_SIMPLE_SPI_GPIO &&
		p->init_code32 && p->init_code_num) {
		if (!boot_cnt++) {
			hw_init_spi(&p->base);
			spi_write_reg_array_gpio(&p->base, p->init_code32,
				p->init_code_num, p->spi_mode);
		} else {
			spi_write_reg_array_api(&p->base, p->init_code32,
				p->init_code_num, p->spi_mode);
		}
	}
	if (p->spi_mode == SPI_4WIRE_MODE)
		gpio_direction_output(p->spi_rs, 1);

	if (!g_buf) {
		width = p->desc->modes->hdisplay;
		height = p->desc->modes->vdisplay;
		pitch = ALIGN(width * 4, 16);
		cma_obj = drm_gem_cma_create(p->base.drm, pitch * height);
		if (IS_ERR_OR_NULL(cma_obj)) {
			err = PTR_ERR(cma_obj);
			DRM_ERROR("cma obj create failed, err = %d\n", err);
			return err;
		}
		objs = &cma_obj->base;

		cmd_mode.width = width;
		cmd_mode.height = height;
		cmd_mode.pixel_format = DRM_FORMAT_ARGB8888;
		cmd_mode.pitches[0] = pitch;
		wb_fb = drm_gem_fb_create_with_objs(p->base.drm,
			&cmd_mode, &objs, 1, NULL);
		if (IS_ERR_OR_NULL(wb_fb)) {
			err = PTR_ERR(wb_fb);
			DRM_ERROR("cma obj create failed, err = %d\n", err);
			return err;
		}
		if (p->spi_mode == SPI_3WIRE_MODE) {
			p->buf_size = pitch * height * 3 / 2;
			g_buf = devm_kzalloc(panel->dev,
					p->buf_size, GFP_KERNEL);
			p->transfer = argb8888_to_2p3t;
		} else {
			p->buf_size = pitch * height * 3 / 4;
			g_buf = devm_kzalloc(panel->dev,
					p->buf_size, GFP_KERNEL);
			p->transfer = argb8888_to_rgb888;
		}
		g_panel = p;
	}
	lombo_crtc_register_panel_flush_cb(spi_panel_flush);
	return 0;
}

static int panel_lombo_spi_enable(struct drm_panel *panel)
{
	struct panel_lombo_spi *p = to_panel_lombo_spi(panel);

	DRM_DEBUG_KMS("[%d] p->enabled:%d\n", __LINE__, p->enabled);

	if (p->enabled)
		return 0;
	p->enabled = true;

	if (p->desc && p->desc->delay.enable)
		msleep(p->desc->delay.enable);

	if (p->backlight) {
		p->backlight->props.state &= ~BL_CORE_FBBLANK;
		p->backlight->props.power = FB_BLANK_UNBLANK;
		backlight_update_status(p->backlight);
	}

	return 0;
}

static int panel_lombo_spi_get_fixed_modes(
	struct panel_lombo_spi *panel)
{
	struct drm_connector *connector = panel->base.connector;
	struct drm_device *drm = panel->base.drm;
	struct drm_display_mode *mode;
	unsigned int i, num = 0;

	if (!panel->desc)
		return 0;

	for (i = 0; i < panel->desc->num_modes; i++) {
		const struct drm_display_mode *m = panel->desc->modes + i;

		mode = drm_mode_duplicate(drm, m);
		if (!mode) {
			DRM_ERROR("failed to add mode %ux%u@%u\n",
				m->hdisplay, m->vdisplay, m->vrefresh);
			continue;
		}

		drm_mode_set_name(mode);
		drm_mode_probed_add(connector, mode);
		num++;
	}

	connector->display_info.bpc = panel->desc->bpc;
	connector->display_info.width_mm = panel->desc->size.width;
	connector->display_info.height_mm = panel->desc->size.height;
	connector->display_info.panel_orientation = panel->orientation;

	return num;
}

static int panel_lombo_spi_get_modes(struct drm_panel *panel)
{
	struct panel_lombo_spi *p = to_panel_lombo_spi(panel);
	int num = 0;

	/* add hard-coded panel modes */
	num += panel_lombo_spi_get_fixed_modes(p);

	return num;
}

static int panel_lombo_spi_get_timings(struct drm_panel *panel,
				    unsigned int num_timings,
				    struct display_timing *timings)
{
	DRM_DEBUG_KMS("%s,%d\n", __func__, __LINE__);
	return 0;
}

static const struct drm_panel_funcs panel_lombo_spi_funcs = {
	.disable = panel_lombo_spi_disable,
	.unprepare = panel_lombo_spi_unprepare,
	.prepare = panel_lombo_spi_prepare,
	.enable = panel_lombo_spi_enable,
	.get_modes = panel_lombo_spi_get_modes,
	.get_timings = panel_lombo_spi_get_timings,
};

static int fill_panel_mode_info(struct panel_lombo_spi_desc *desc,
	struct device_node *np)
{
	u32 val = 0, values[9] = { 0 };

	if (!of_property_read_u32_array(np, "timing", values, 9)) {
		desc->modes->clock = values[0] / 1000;
		desc->modes->hdisplay = values[4];
		desc->modes->hsync_start = values[1] + values[4];
		desc->modes->hsync_end = values[1] + values[2] + values[4];
		desc->modes->htotal = values[1] + values[2] +
				      values[3] + values[4];

		desc->modes->vdisplay = values[8];
		desc->modes->vsync_start = values[5] + values[8];
		desc->modes->vsync_end = values[5] + values[6] + values[8];
		desc->modes->vtotal = values[5] + values[6] +
				       values[7] + values[8];

		val = desc->modes->htotal * desc->modes->vtotal;
		desc->modes->vrefresh = values[0] / val;
	}
	DRM_DEBUG("clock: %d\n", desc->modes->clock);
	DRM_DEBUG("hdisplay: %d\n", desc->modes->hdisplay);
	DRM_DEBUG("hsync_start: %d\n", desc->modes->hsync_start);
	DRM_DEBUG("hsync_end: %d\n", desc->modes->hsync_end);
	DRM_DEBUG("htotal: %d\n", desc->modes->htotal);
	DRM_DEBUG("vdisplay: %d\n", desc->modes->vdisplay);
	DRM_DEBUG("vsync_start: %d\n", desc->modes->vsync_start);
	DRM_DEBUG("vsync_end: %d\n", desc->modes->vsync_end);
	DRM_DEBUG("vtotal: %d\n", desc->modes->vtotal);
	DRM_DEBUG("vrefresh: %d\n", desc->modes->vrefresh);

	if (of_property_read_bool(np, "is_preferred_mode"))
		desc->modes->type |= DRM_MODE_TYPE_PREFERRED;
	DRM_DEBUG("is_preferred_mode: %s\n", desc->modes->type &
		DRM_MODE_TYPE_PREFERRED ? "yes" : "no");

	return 0;
}

static void parse_delay_time(struct panel_lombo_spi_desc *desc,
	struct device_node *np)
{
	u32 val[5];

	if (!of_property_read_u32_array(np, "delay", val, 5)) {
		desc->delay.reset = val[0];
		desc->delay.prepare = val[1];
		desc->delay.enable = val[2];
		desc->delay.disable = val[3];
		desc->delay.unprepare = val[4];
	}
	DRM_DEBUG("delay_reset: %d\n", desc->delay.reset);
	DRM_DEBUG("delay_enable: %d\n", desc->delay.enable);
	DRM_DEBUG("delay_disable: %d\n", desc->delay.disable);
	DRM_DEBUG("delay_prepare: %d\n", desc->delay.prepare);
	DRM_DEBUG("delay_unprepare: %d\n", desc->delay.unprepare);
}

static void parse_panel_physical_size(struct panel_lombo_spi_desc *desc,
	struct device_node *np)
{
	u32 val[2];

	if (!of_property_read_u32_array(np, "size-mm", val, 2)) {
		desc->size.width = val[0];
		desc->size.height = val[1];
	}
}

static int fill_vo_dev_info(struct panel_lombo_spi_desc *desc,
	struct device_node *np)
{
	fill_panel_mode_info(desc, np);
	parse_delay_time(desc, np);
	parse_panel_physical_size(desc, np);

	return 0;
}

static int __parse_init_code32(struct device *dev, u32 **code, u32 *num)
{
	const char *prop = "init_code32";
	int cnt;
	u32 *val;

	cnt = of_property_count_u32_elems(dev->of_node, prop);
	if (cnt <= 0)
		return -ENODEV;

	val = devm_kzalloc(dev, 4 * cnt, GFP_KERNEL);
	if (!val)
		return -ENOMEM;

	if (of_property_read_u32_array(dev->of_node, prop, val, cnt))
		return -ENODEV;

	*code = val;
	*num = cnt;

	return 0;
}

static int panel_spi_init_panel_host(struct panel_lombo_spi *p)
{
	struct device *dev = p->dev;
	const char *str = NULL;

	if (!of_property_read_string(dev->of_node, "trans_if", &str)) {
		struct panel_lombo_spi_desc *desc = p->desc;

		if (!strcmp(str, "spi")) {
			desc->init_panel = PANEL_SIMPLE_SPI;
			__parse_init_code32(dev, &p->init_code32,
				&p->init_code_num);
		} else if (!strcmp(str, "spi_gpio")) {
			desc->init_panel = PANEL_SIMPLE_SPI_GPIO;
			__parse_init_code32(dev, &p->init_code32,
				&p->init_code_num);
		} else {
			DRM_INFO("unspport trans_if=%s\n", str);
		}
	}

	return 0;
}

static struct panel_lombo_spi_desc *panel_lombo_spi_probe(
	struct device *dev)
{
	struct panel_lombo_spi *panel;
	struct device_node *np = dev->of_node, *np_bl;
	enum of_gpio_flags flags;
	int err;
	int rotate = 0;
	const char *label;
	const char *str = NULL;
	struct panel_lombo_spi_desc *desc_spi, *desc =
		(struct panel_lombo_spi_desc *)of_device_get_match_data(dev);

	panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL);
	if (!panel)
		return NULL;

	panel->reset_gpio =
		of_get_named_gpio_flags(np, "reset_gpio", 0, &flags);
	panel->reset_gpio_flag = flags;
	DRM_DEBUG_DRIVER("reset_gpio %d, flags %d\n", panel->reset_gpio, flags);
	if (!gpio_is_valid(panel->reset_gpio)) {
		DRM_DEBUG_DRIVER("reset_gpio %d invalid\n", panel->reset_gpio);
		panel->reset_gpio = 0;
	} else {
		err = devm_gpio_request(dev, panel->reset_gpio, "reset_gpio");
		if (err < 0) {
			DRM_ERROR("power_gpio %d request failed\n",
				panel->reset_gpio);
			panel->reset_gpio = 0;
		}
	}

	panel->power_gpio =
		of_get_named_gpio_flags(np, "power_gpio", 0, &flags);
	panel->power_gpio_flag = flags;
	DRM_DEBUG_DRIVER("power_gpio %d, flags %d\n", panel->power_gpio, flags);
	if (!gpio_is_valid(panel->power_gpio)) {
		DRM_DEBUG_DRIVER("power_gpio %d invalid\n", panel->power_gpio);
		panel->power_gpio = 0;
	} else {
		err = devm_gpio_request(dev, panel->power_gpio, "power_gpio");
		if (err < 0) {
			DRM_ERROR("power_gpio %d request failed\n",
				panel->power_gpio);
			panel->power_gpio = 0;
		}
	}

	panel->sel_gpio =
		of_get_named_gpio_flags(np, "sel_gpio", 0, &flags);
	panel->sel_gpio_flag = flags;
	DRM_DEBUG_DRIVER("sel_gpio %d, flags %d\n", panel->sel_gpio, flags);
	if (!gpio_is_valid(panel->sel_gpio)) {
		DRM_DEBUG_DRIVER("sel_gpio %d invalid\n", panel->sel_gpio);
		panel->sel_gpio = 0;
	} else {
		err = devm_gpio_request(dev, panel->sel_gpio, "sel_gpio");
		if (err < 0) {
			DRM_ERROR("sel_gpio %d request failed\n",
				panel->sel_gpio);
			panel->sel_gpio = 0;
		}
	}

	label = "spi_mode";
	if (!of_property_read_string(np, label, &str)) {
		if (!strcmp(str, "3wire")) {
			panel->spi_mode = SPI_3WIRE_MODE;
		} else if (!strcmp(str, "4wire")) {
			panel->spi_mode = SPI_4WIRE_MODE;
		} else {
			DRM_INFO("unspport spi_mode=%s\n", str);
			panel->spi_mode = SPI_3WIRE_MODE;
		}
	}

	err = -EINVAL;
	label = "spi_sda_gpio";
	panel->spi_data = of_get_named_gpio_flags(np, label, 0, &flags);
	if (gpio_is_valid(panel->spi_data))
		err = devm_gpio_request(dev, panel->spi_data, label);
	if (!err) {
		panel->spi_data_flag = flags;
		DRM_DEBUG("%s %d, flags 0x%x\n", label, panel->spi_data, flags);
	} else {
		panel->spi_data = 0;
		DRM_DEBUG("%s request failed\n", label);
	}

	err = -EINVAL;
	label = "spi_cs_gpio";
	panel->spi_cs = of_get_named_gpio_flags(np, label, 0, &flags);
	if (gpio_is_valid(panel->spi_cs))
		err = devm_gpio_request(dev, panel->spi_cs, label);
	if (!err) {
		panel->spi_cs_flag = flags;
		DRM_DEBUG("%s %d, flags 0x%x\n", label, panel->spi_cs, flags);
	} else {
		panel->spi_cs = 0;
		DRM_DEBUG("%s request failed\n", label);
	}

	err = -EINVAL;
	label = "spi_rs_gpio";
	panel->spi_rs = of_get_named_gpio_flags(np, label, 0, &flags);
	if (gpio_is_valid(panel->spi_rs))
		err = devm_gpio_request(dev, panel->spi_rs, label);
	if (!err) {
		panel->spi_rs_flag = flags;
		DRM_DEBUG("%s %d, flags 0x%x\n", label, panel->spi_rs, flags);
	} else {
		panel->spi_rs = 0;
		DRM_DEBUG("%s request failed\n", label);
	}

	err = -EINVAL;
	label = "spi_scl_gpio";
	panel->spi_clk = of_get_named_gpio_flags(np, label, 0, &flags);
	if (gpio_is_valid(panel->spi_clk))
		err = devm_gpio_request(dev, panel->spi_clk, label);
	if (!err) {
		panel->spi_clk_flag = flags;
		DRM_DEBUG("%s %d, flags 0x%x\n", label, panel->spi_clk, flags);
	} else {
		panel->spi_clk = 0;
		DRM_DEBUG("%s request failed\n", label);
	}

	panel->spi_workq = create_singlethread_workqueue("spi_workp");
	if (!panel->spi_workq) {
		DRM_ERROR("failed to create workqueue\n");
		return NULL;
	}
	INIT_WORK(&panel->spi_flush_work,
		  panel_lombo_spi_flush_worker);
	INIT_WORK(&panel->spi_write_work,
		  panel_lombo_spi_write_worker);

	np_bl = of_parse_phandle(np, "backlight", 0);
	if (np_bl) {
		panel->backlight = of_find_backlight_by_node(np_bl);
		of_node_put(np_bl);
		if (!panel->backlight)
			DRM_ERROR("no find backlight device\n");
	}

	if (!of_property_read_u32(np, "rotate", &rotate)) {
		if (rotate == 90)
			panel->orientation =
				DRM_MODE_PANEL_ORIENTATION_LEFT_UP;
		else if (rotate == 180)
			panel->orientation =
				DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP;
		else if (rotate == 270)
			panel->orientation =
				DRM_MODE_PANEL_ORIENTATION_RIGHT_UP;
	}

	if (!desc) {
		panel->desc = devm_kzalloc(dev,
			sizeof(*panel->desc), GFP_KERNEL);
		if (!panel->desc)
			return NULL;
		desc_spi = panel->desc;
		desc_spi->modes = devm_kzalloc(dev,
			sizeof(struct drm_display_mode), GFP_KERNEL);
		if (!desc_spi->modes)
			return NULL;
		fill_vo_dev_info(desc_spi, np);
		desc_spi->num_modes = 1;
	} else {
		desc_spi = desc;
	}

	panel->enabled = false;
	panel->prepared = false;
	panel->dev = dev;
	panel->desc = desc_spi;

	drm_panel_init(&panel->base);
	panel->base.dev = dev;
	panel->base.funcs = &panel_lombo_spi_funcs;

	err = drm_panel_add(&panel->base);
	if (err < 0)
		goto err_out;
	panel_spi_init_panel_host(panel);
	dev_set_drvdata(dev, panel);

	return desc_spi;
err_out:
	put_device(&panel->backlight->dev);
	return NULL;
}

static int panel_lombo_spi_vo_probe(struct lombo_vo_device *vo)
{
	struct device *dev = &vo->dev;
	struct panel_lombo_spi_desc *desc;

	desc = panel_lombo_spi_probe(dev);
	if (!desc) {
		DRM_ERROR("panel_lombo_spi_probe err\n");
		return -EINVAL;
	}

	return lombo_vo_attach(vo);
}

static int panel_lombo_spi_vo_remove(struct lombo_vo_device *vo)
{
	struct panel_lombo_spi *panel = dev_get_drvdata(&vo->dev);

	lombo_vo_detach(vo);
	drm_panel_detach(&panel->base);
	drm_panel_remove(&panel->base);

	panel_lombo_spi_disable(&panel->base);

	if (panel->backlight)
		put_device(&panel->backlight->dev);

	return 0;
}

static void panel_lombo_spi_vo_shutdown(struct lombo_vo_device *vo)
{
	struct panel_lombo_spi *panel = dev_get_drvdata(&vo->dev);

	panel_lombo_spi_disable(&panel->base);
}

static const struct of_device_id panel_lombo_spi_vo_of_match[] = {
	{
		.compatible = "lombo,panel_spi",
	}, {
		/* sentinel */
	}
};
MODULE_DEVICE_TABLE(of, panel_lombo_spi_vo_of_match);

static struct lombo_vo_driver panel_lombo_spi_vo_driver = {
	.driver = {
		.name = "panel-lombo-spi-vo",
		.of_match_table = panel_lombo_spi_vo_of_match,
	},
	.probe = panel_lombo_spi_vo_probe,
	.remove = panel_lombo_spi_vo_remove,
	.shutdown = panel_lombo_spi_vo_shutdown,
};

static int panel_spi_probe(struct spi_device *spi)
{
	int err = 0;
	struct device_node *np = spi->dev.of_node;

	spi_client = kzalloc(sizeof(struct lombo_spi_client), GFP_KERNEL);
	if (!spi_client) {
		DRM_ERROR("malloc spi_client failed\n");
		return -ENOMEM;
	}

	spi_client->spi = spi;
	spi_client->page_size = MAX_BUF_SIZE;
	mutex_init(&spi_client->lock);
	dev_set_drvdata(&spi->dev, spi_client);

	err = of_property_read_u32(np, "spi-max-frequency",
				   &spi_client->freq);
	if (err) {
		DRM_ERROR("Fail to get frequency\n");
		goto err_get_frequency;
	}

	spi_client->name = dev_name(&spi->dev);
	spi_client->dev.parent = &spi->dev;

	DRM_INFO("spi_client name=%s\n", spi_client->name);

	return 0;

err_get_frequency:
	kfree(spi_client);

	return err;
}

static int panel_spi_remove(struct spi_device *spi)
{
	kfree(spi_client);
	return 0;
}

static const struct spi_device_id panel_lombo_simple_spi_id[] = {
	{ "panel_simple_spi", 0 },
	{ },
};

static const struct of_device_id panel_spi_match[] = {
	{ .compatible = "lombo,panel_spi", },
	{},
};

struct spi_driver panel_spi_driver = {
	.driver = {
		.name = "lombo_panel_spi",
		.owner = THIS_MODULE,
		.of_match_table = panel_spi_match,
	},
	.id_table = panel_lombo_simple_spi_id,
	.probe = panel_spi_probe,
	.remove = panel_spi_remove,
};

static int __init panel_lombo_spi_init(void)
{
	int err;

	err = lombo_vo_driver_register(&panel_lombo_spi_vo_driver);
	if (err < 0)
		return err;

	err = spi_register_driver(&panel_spi_driver);
	if (err < 0) {
		DRM_ERROR("init panel spi driver failed\n");
		return err;
	}

	return 0;
}
module_init(panel_lombo_spi_init);

static void __exit panel_lombo_spi_exit(void)
{
	spi_unregister_driver(&panel_spi_driver);
	lombo_vo_driver_unregister(&panel_lombo_spi_vo_driver);
}
module_exit(panel_lombo_spi_exit);

MODULE_AUTHOR("lomboswer <lomboswer@lombotech.com>");
MODULE_DESCRIPTION("Spi Panel Driver");
MODULE_LICENSE("GPL and additional rights");
